﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SimpleTaskManager.Domain.Abstract;
using SimpleTaskManager.Domain.Entities;
using SimpleTaskManager.WebUI.Models;

namespace SimpleTaskManager.WebUI.Controllers
{
    /// <summary>
    /// The controller responsible for performing actions connected to task management
    /// </summary>
    public class TaskController : Controller
    {
        private ITaskRepository repository;
        private IUserRepository userRepository;

        /// <summary>
        /// Creates a new instance of the task controller.
        /// </summary>
        /// <param name="taskRepository">A repository containing the tasks in the database.</param>
        /// <param name="userRepository">A repository containing the users in the database.</param>
        public TaskController(ITaskRepository taskRepository, IUserRepository userRepository)
        {
            repository = taskRepository;
            this.userRepository = userRepository;
        }

        #region SubTasks
        /// <summary>
        /// Renders the details (basic) view for a given task.
        /// </summary>
        /// <param name="id">The ID of the task which details will be shown.</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        public ActionResult Details(int id)
        {
            //Get the current task
            var task = GetTaskByID(id);
            //error if not found
            if (task == null)
                return HttpNotFound();

            //Set whether the current user is the leader of the current task
            ViewBag.IsLeader = IsUserLeader(task);

            return View(task);
        }

        /// <summary>
        /// Renders the header for the task Details/People/Reports pages.
        /// </summary>
        /// <param name="id">The ID of the task</param>
        /// <param name="page">Indicates the active page (0: Details(Tasks), 1: People, 2: Reports)</param>
        /// <returns>The rendered view</returns>
        public ActionResult Header(int id, int page)
        {
            //Get the current task
            var task = GetTaskByID(id);
            //error if not found
            if (task == null)
                return HttpNotFound();

            //Pass the parameter of which page is active to the view
            ViewBag.SelectedPage = page;
            return View(task);
        }

        /// <summary>
        /// Renders the footer for the task Details/People/Reports pages.
        /// </summary>
        /// <param name="id">The ID of the task</param>
        /// <returns>The rendered view</returns>
        public ActionResult Footer(int id)
        {
            //Get the current task
            var task = GetTaskByID(id);

            //Set whether the current user is the leader of the current task
            ViewBag.IsLeader = IsUserLeader(task);

            return View(task);
        }

        //
        // GET: /Task/Create/id
        /// <summary>
        /// Renders the task creation view.
        /// </summary>
        /// <param name="parentId">The ID of the task which will be the parent of the new task. 
        /// To create a root task (aka project) pass -1</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        public ActionResult Create(int parentId)
        {
            //Get the current task
            var task = GetTaskByID(parentId);

            //If the current user is not the task leader, then don't allow
            if (parentId != -1 && !IsUserLeader(task))
                return HttpNotFound();

            //Make the parent's id accessible for the view
            ViewBag.ParentId = parentId;

            //Pass a list of the users to allow the selection of a leader
            ViewBag.UserList = userRepository.Users.AsEnumerable();

            return View();
        }

        //
        // POST: /User/Create
        /// <summary>
        /// Adds the created task (form result) to the tasks, as a subtask for the specified parent task.
        /// </summary>
        /// <param name="task">The newly created task</param>
        /// <param name="parentId">The ID of the task which will be the parent of the new task. 
        /// To create a root task (aka project) pass -1</param>
        /// <returns>Redirects to the parent task if the task was created succesful.
        /// Displays errors on the creation page otherwise.</returns>
        [Authorize]
        [HttpPost]
        public ActionResult Create(SimpleTaskManager.Domain.Entities.Task task, int parentId)
        {
            //if task with such a name already exists return to the view
            if (ModelState.IsValidField("Name") &&
                repository.Tasks.Any(x => x.Name.Equals(task.Name, StringComparison.OrdinalIgnoreCase)))
            {
                ModelState.AddModelError("Name", "That name is already taken.");
            }
            //if the deadline is in the past
            if (ModelState.IsValidField("EndDate") && task.EndDate.CompareTo(DateTime.Now) <= 0)
            {
                ModelState.AddModelError("EndDate", "The deadline has to be in the future.");
            }
            if (ModelState.IsValid)
            {
                repository.CreateSubTask(parentId, task);
                //If the task has a parent
                if (parentId != -1)
                {
                    // then redirect to its details page
                    return RedirectToAction("Details", new { id = parentId });
                }
                else
                {
                    // else display the projects list
                    return RedirectToAction("Index", "Projects");
                }
            }
            ViewBag.ParentId = parentId;

            //Pass a list of the users to allow the selection of a leader
            ViewBag.UserList = userRepository.Users.AsEnumerable();

            return View(task);
        }

        //
        // GET: /Task/Edit/5
        /// <summary>
        /// Renders the edit details view for a given task.
        /// </summary>
        /// <param name="id">The ID of the task</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        [ActionName("Edit")]
        public ActionResult Edit(int id)
        {
            //Get the task
            var task = GetTaskByID(id);
            //Error if the user is not the leader of this task
            if (!IsUserLeader(task))
                return HttpNotFound();

            //Pass a list of the users to allow the selection of a leader
            ViewBag.UserList = userRepository.Users.AsEnumerable();

            //Render the view
            return View(task);
        }

        //
        // POST: /Task/Edit/5
        /// <summary>
        /// Sets the modified properties of a task.
        /// </summary>
        /// <param name="task">The modified task entity</param>
        /// <returns>Redirects to the details view if the modification were valid.
        /// Displays errors on the edit page otherwise.</returns>
        [Authorize]
        [HttpPost]
        public ActionResult Edit(SimpleTaskManager.Domain.Entities.Task task)
        {
            //if task with such a name already exists return to the view
            if (ModelState.IsValidField("Name") &&
                repository.Tasks.Any(x => x.Name.Equals(task.Name, StringComparison.OrdinalIgnoreCase) 
                    && x.TaskID != task.TaskID))
            {
                ModelState.AddModelError("Name", "That name is already taken.");
            }

            if (ModelState.IsValid)
            {
                repository.EditTask(task);
                return RedirectToAction("Details", new { id = (task.ParentTask ?? task).TaskID });
            }

            //Pass a list of the users to allow the selection of a leader
            ViewBag.UserList = userRepository.Users.AsEnumerable();
            return View(task);
        }

        //
        // GET: /Task/Delete/5
        /// <summary>
        /// Renders a confirmation page for a task deletion.
        /// </summary>
        /// <param name="id">The ID of the task to delete</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        public ActionResult Delete(int id)
        {
            var task = GetTaskByID(id);
            if (!IsUserLeader(task))
                return HttpNotFound();

            return View(task);
        }

        //
        // POST: /Task/Delete/5
        /// <summary>
        /// Deletes a given task.
        /// </summary>
        /// <param name="id">The ID of the task to delete</param>
        /// <returns>Redirects to the parent task's details page or to the projects lists</returns>
        [Authorize]
        [HttpPost, ActionName("Delete")]
        public ActionResult DeleteConfirmed(int id)
        {
            var parent = GetTaskByID(id).ParentTask;
            repository.DeleteTask(id);
            return (parent != null)
                ? RedirectToAction("Details", new { id = parent.TaskID})
                : RedirectToAction("Index", "Projects");
        }

        /// <summary>
        /// Renders a partial view to manage a task's required tasks.
        /// </summary>
        /// <param name="id">The ID of the task</param>
        /// <returns>The rendered partial view</returns>
        [Authorize]
        public ActionResult AddRequirement(int id)
        {
            var task = GetTaskByID(id);

            //Pass a list of the sibling tasks, which can possible requirements
            ViewBag.Siblings = (task.ParentTask != null) ? task.ParentTask.SubTasks.OrderBy(t => t.Name).AsEnumerable()
                : (new List<Task>()).AsEnumerable(); // or an empty list of no parent task (and by this siblings) exists

            return PartialView(task);
        }

        /// <summary>
        /// Sets the task's requirements according to the form result.
        /// </summary>
        /// <param name="formData">The data from the form displayed by the AddRequirement action</param>
        /// <param name="id">The ID of the task which requirements will be set.</param>
        /// <returns>A JSON result indicating success</returns>
        [Authorize]
        [HttpPost]
        public ActionResult AddRequirement(FormCollection formData, int id)
        {
            var task = GetTaskByID(id);

            string[] selectedTaskIds = Request.Form.GetValues("tasks");
            //if no checkbox was checked
            if (selectedTaskIds == null)
            {
                //clear the requirements list
                while (task.Requirements.Count > 0)
                {
                    //Get them, and remove them from the requirements
                    repository.RemoveRequirement(task.TaskID,
                        task.Requirements.First().TaskID);
                }
            }
            else
            {
                //Convert the ids to int
                List<int> convertedTaskIds = new List<int>();
                selectedTaskIds.ToList().ForEach(t => convertedTaskIds.Add(int.Parse(t)));

                //Add new requirements to the task
                foreach (int reqId in convertedTaskIds)
                {
                    //If the task doesn't contain this requirement yet
                    if (!task.Requirements.Any(t => t.TaskID.Equals(reqId)))
                    {
                        //then add it
                        repository.AddRequirement(task.TaskID, reqId);
                    }
                }
                //Remove the tasks from the requirements which are not selected
                //While there are tasks in the requirements which are not on the list
                while (task.Requirements.Any(t => !convertedTaskIds.Contains(t.TaskID)))
                {
                    //Get them, and remove them from the requirements
                    repository.RemoveRequirement(task.TaskID,
                        task.Requirements.First(t => !convertedTaskIds.Contains(t.TaskID)).TaskID);
                }
            }
            return Json(new { Success = true });
        }

        /// <summary>
        /// Renders the list of the sub tasks for a given task as a partial view.
        /// </summary>
        /// <param name="id">The ID of the task which subtasks will be listed</param>
        /// <returns>The rendered partial view</returns>
        public ActionResult SubTaskList(int id)
        {
            var task = GetTaskByID(id);

            //Set whether the current user is the leader of the current task
            ViewBag.IsLeader = IsUserLeader(task);

            return PartialView("_SubTaskListPartial", task);
        }
        #endregion

        #region People
        //
        // GET: /Task/People/5
        /// <summary>
        /// Renders a view displaying the people assigned to a given task and its subtasks.
        /// </summary>
        /// <param name="id">The ID of the task</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        public ActionResult People(int id)
        {
            var task = GetTaskByID(id);
            //Set whether the current user is the leader of the current task
            ViewBag.IsLeader = IsUserLeader(task);
            return View(task);
        }

        /// <summary>
        /// Renders a partial view with the list of the people assigned to a given task.
        /// </summary>
        /// <param name="id">The ID of the task</param>
        /// <returns>The rendered list as a partial view</returns>
        public ActionResult PeopleListPartial(int id)
        {
            var task = GetTaskByID(id);
            return PartialView("_PeopleListPartial", task.Group.AsEnumerable());
        }

        /// <summary>
        /// Renders a form to manage the assigned people for a given task as a partial view.
        /// </summary>
        /// <param name="id">The ID of the task</param>
        /// <returns>The rendered form as a partial view</returns>
        public ActionResult AssignPeople(int id)
        {
            var task = GetTaskByID(id);
            ViewBag.UserList = (task.ParentTask != null)
                ? task.ParentTask.Group.Where(u => u.StatusRep == (int)StatusEnum.Hired).AsEnumerable()
                : userRepository.Users.Where(u => u.StatusRep == (int)StatusEnum.Hired).AsEnumerable();
            return PartialView(task);
        }

        /// <summary>
        /// Sets the list of the assigned people according to the passed form results for a task.
        /// </summary>
        /// <param name="formData">The data recieved from the form rendered by the AssignPeople view</param>
        /// <param name="id">The ID of the task</param>
        /// <returns>A JSON result indicating success</returns>
        [HttpPost]
        public ActionResult AssignPeople(FormCollection formData, int id)
        {
            var task = GetTaskByID(id);
            string[] selectedUserIds = Request.Form.GetValues("users");
            //if no checkbox was checked
            if (selectedUserIds == null)
            {
                //clear the group list
                while (task.Group.Count > 0)
                {
                    //Get them, and remove them the group
                    repository.RemoveUserFromGroup(task.TaskID, task.Group.First().UserID);
                }
            }
            else
            {
                //Convert the ids to int
                List<int> convertedUserIds = new List<int>();
                selectedUserIds.ToList().ForEach(u => convertedUserIds.Add(int.Parse(u)));

                //Add new users to the group
                foreach (int userId in convertedUserIds)
                {
                    //If the group doesn't contain this user yet
                    if (!task.Group.Any(u => u.UserID.Equals(userId)))
                    {
                        //then add him
                        repository.AssignUserToTask(task.TaskID, userId);
                    }
                }
                //Remove the users from the group who are not selected
                //While there are users in the group who are not on the list
                while (task.Group.Any(u => !convertedUserIds.Contains(u.UserID)))
                {
                    //Get them, and remove them the group
                    repository.RemoveUserFromGroup(task.TaskID,
                        task.Group.First(u => !convertedUserIds.Contains(u.UserID)).UserID);
                }
            }
            return Json(new { Success = true });
        }

        /// <summary>
        /// Renders a list of the people in the group (in a paragraph format opposed to a list layout) of a given task.
        /// Indicates the number of people in the group and the number of required people in the beginning of the resulting partial view.
        /// </summary>
        /// <param name="id">The ID of the task</param>
        /// <returns>The rendered partial view</returns>
        public ActionResult GroupList(int id)
        {
            var task = GetTaskByID(id);

            return PartialView("_GroupListPartial", task);
        }

        /// <summary>
        /// Renders the list of the sub tasks displaying the people associated with the sub task for a given task as a partial view.
        /// </summary>
        /// <param name="id">The ID of the task which subtasks will be listed</param>
        /// <returns>The rendered partial view</returns>
        public ActionResult SubtaskPeopleList(int id)
        {
            var task = GetTaskByID(id);
            //Set whether the current user is the leader of the current task
            ViewBag.IsLeader = IsUserLeader(task);
            return PartialView("_SubtaskPeopleListPartial", task);
        }
        #endregion

        #region Reports
        //
        // GET: /Task/Reports/5
        /// <summary>
        /// Renders a view displaying the reports for a given task.
        /// </summary>
        /// <param name="id">The ID of the task</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        public ActionResult Reports(int id)
        {
            var task = GetTaskByID(id);

            //Pass the reports of the task to the view
            ViewBag.Reports = task.Reports;

            //Can Upload report if the current user is in the group of this project 
            //or if he is the leader of the project
            ViewBag.CanAddReport = IsUserInGroupOrLeader(task); 

            return View(task);
        }

        //
        // GET: /Task/UploadReport/5
        /// <summary>
        /// Renders a form to upload a report for a task.
        /// </summary>
        /// <param name="id">The ID of the task for which the report will be uploaded</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        public ActionResult UploadReport(int id)
        {
            var task = GetTaskByID(id);
            if (!IsUserInGroupOrLeader(task))
            {
                //error
                return HttpNotFound();
            }
            ViewBag.TaskId = id;
            return View();
        }

        //
        // POST: /Task/UploadReport/5
        /// <summary>
        /// Adds the given report to the specified task.
        /// </summary>
        /// <param name="report">The uploaded report</param>
        /// <param name="id">The ID of the task to which the report will be added</param>
        /// <returns>Redirects to the list of the reports if the upload is successful.
        /// Displays errors on the upload page otherwise.</returns>
        [Authorize]
        [HttpPost]
        public ActionResult UploadReport(ReportHeader report, int id)
        {
            var task = GetTaskByID(id);

            if (IsUserInGroupOrLeader(task))
            {
                UploadedFile file = RetrieveFileFromRequest();
                if (file != null && file.FileSize > 0 && ModelState.IsValid)
                {
                    //Add the report to the task
                    repository.AddReport(id, report.PrepareReport(file, GetCurrentUser()));
                    return RedirectToAction("Reports", new { id = id });
                }
                else
                {
                    ModelState.AddModelError("File", "Select a file with a length greater than 0.");
                }
            }
            ViewBag.TaskId = id;
            return View(report);
        }

        //
        // GET: /Task/ReportDetails/
        /// <summary>
        /// Renders a view displaying details about a given report.
        /// </summary>
        /// <param name="taskId">The ID of the task which contains the report</param>
        /// <param name="reportId">The ID of the report</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        public ActionResult ReportDetails(int taskId, int reportId)
        {
            var task = GetTaskByID(taskId);
            var report = task.Reports.Single(r => r.ReportHeaderID.Equals(reportId));

            //Can approve if the user is the leader of this or any parent task
            ViewBag.CanApprove = IsUserLeader(task);
            //Can open if can approve or the user is the original uploader
            ViewBag.CanOpen = ViewBag.CanApprove || report.CreatedBy.Login.Equals(User.Identity.Name);

            ViewBag.TaskId = taskId;
            return View(report);
        }

        /// <summary>
        /// Gets the data associated with a given report.
        /// </summary>
        /// <param name="taskId">The ID of the task which contains the report</param>
        /// <param name="reportId">The ID of the report</param>
        /// <returns>The binary data of the report</returns>
        [Authorize]
        public ActionResult ReportData(int taskId, int reportId)
        {
            var task = GetTaskByID(taskId);
            var report = task.Reports.Single(r => r.ReportHeaderID.Equals(reportId));

            if ((IsUserLeader(task) || report.CreatedBy.Login.Equals(User.Identity.Name))
                && report.Details != null)
            {
                return new BinaryResult(report.Details.Data, report.Details.ContentType, report.Details.FileName);
            }

            return HttpNotFound();
        }

        //
        // GET: /Task/ApproveReport/
        /// <summary>
        /// Renders a view to confirm approvement of a report.
        /// </summary>
        /// <param name="taskId">The ID of the task which contains the report</param>
        /// <param name="reportId">The ID of the report</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        public ActionResult ApproveReport(int taskId, int reportId)
        {
            var task = GetTaskByID(taskId);
            var report = task.Reports.Single(r => r.ReportHeaderID.Equals(reportId));

            if (!IsUserLeader(task))
                return HttpNotFound();
            ViewBag.TaskId = taskId;

            return View(report);
        }

        //
        // POST: /Task/ApproveReport
        /// <summary>
        /// Marks a given report approved by the current user.
        /// </summary>
        /// <param name="taskId">The ID of the task which contains the report</param>
        /// <param name="reportId">The ID of the report</param>
        /// <returns>Redirects to the details page of the report</returns>
        [Authorize]
        [HttpPost, ActionName("ApproveReport")]
        public ActionResult ApproveReportConfirmed(int taskId, int reportId)
        {
            repository.ApproveReport(taskId, reportId, GetCurrentUser().UserID);
            return RedirectToAction("ReportDetails", new { taskId = taskId, reportId = reportId });
        }
        #endregion

        /// <summary>
        /// Renders a partial view for the navigation of a task hierarchy to the given task.
        /// </summary>
        /// <param name="id">The ID of the task</param>
        /// <returns>The rendered partial navigation view</returns>
        public ActionResult TaskStructure(int id)
        {
            var task = GetTaskByID(id);
            var parents = new List<SimpleTaskManager.Domain.Entities.Task>();
            if (task != null)
            {
                var parent = task.ParentTask;
                //while the task has a parent
                while (parent != null)
                {
                    //add the parent to the list
                    parents.Add(parent);
                    //get its parent
                    parent = parent.ParentTask;
                }

                //Reverse the list to display the correct order
                parents.Reverse();
            }

            return PartialView("_TaskStructurePartial", parents.AsEnumerable());
        }
        
        #region Helper methods
        /// <summary>
        /// Returns the task identified by the given ID
        /// </summary>
        /// <param name="id">The task's ID</param>
        /// <returns>The task</returns>
        private SimpleTaskManager.Domain.Entities.Task GetTaskByID(int id)
        {
            return repository.Tasks.SingleOrDefault(p => p.TaskID.Equals(id));
        }

        /// <summary>
        /// Returns whether the current user is the leader of the specified task
        /// </summary>
        /// <param name="task">The task to check</param>
        /// <returns>Indicates whether the user is the leader</returns>
        private bool IsUserLeader(SimpleTaskManager.Domain.Entities.Task task)
        {
            if (task == null) return false;
            if (task.Leader == null) return IsUserLeader(task.ParentTask);
            return task.Leader.Login.Equals(User.Identity.Name) || IsUserLeader(task.ParentTask);
        }

        /// <summary>
        /// Indicates whether the current user is the leader or in the group of a specified task.
        /// </summary>
        /// <param name="task">The task to check</param>
        /// <returns>True if the user is involved in the task</returns>
        private bool IsUserInGroupOrLeader(SimpleTaskManager.Domain.Entities.Task task)
        {
            return task.Group.Any(u => u.Login.Equals(User.Identity.Name))
                || task.Leader.Login.Equals(User.Identity.Name);
        }

        /// <summary>
        /// Gets the user entity which represent the current user
        /// </summary>
        /// <returns>The current user's entity</returns>
        private User GetCurrentUser()
        {
            return userRepository.Users.Single(u => u.Login.Equals(User.Identity.Name));
        }

        /// <summary>
        /// Gets the uploaded file from the Request
        /// <remarks>
        /// source: http://buildstarted.com/2011/07/17/asp-net-mvc-3-file-uploads-using-the-fileapi/
        /// </remarks>
        /// </summary>
        /// <returns>The uploaded file</returns>
        private UploadedFile RetrieveFileFromRequest()
        {
            string filename = null;
            string fileType = null;
            byte[] fileContents = null;

            if (Request.Files.Count > 0)
            { //they're uploading the old way
                var file = Request.Files[0];
                fileContents = new byte[file.ContentLength];
                file.InputStream.Read(fileContents, 0, Request.ContentLength);
                fileType = file.ContentType;
                filename = file.FileName;
            }
            else if (Request.ContentLength > 0)
            {
                fileContents = new byte[Request.ContentLength];
                Request.InputStream.Read(fileContents, 0, Request.ContentLength);
                filename = Request.Headers["X-File-Name"];
                fileType = Request.Headers["X-File-Type"];
            }

            return new UploadedFile()
            {
                Filename = filename,
                ContentType = fileType,
                FileSize = fileContents != null ? fileContents.Length : 0,
                Contents = fileContents
            };
        }
        #endregion
    }
}
